package com.gitee.wsl.time.format

import com.gitee.wsl.text.reader.MicroStrReader
import com.gitee.wsl.ext.string.substringUseLength
import com.gitee.wsl.transform.format.Format
import com.gitee.wsl.time.TimeSpan
import com.gitee.wsl.time.exception.DateException
import com.gitee.wsl.time.local.TimezoneNames
import com.gitee.wsl.time.toDuration
import com.gitee.wsl.time.unaryPlus
import kotlinx.datetime.LocalTime
import kotlin.math.absoluteValue
import kotlin.math.floor
import kotlin.math.pow
import kotlin.math.round
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes

interface TimeFormat: Format<TimeSpan> {
    //override fun invoke(p1: TimeSpan): String = format(p1)

    override fun format(dd: TimeSpan): String

    fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean = true): TimeSpan?

    companion object {
        val DEFAULT_FORMAT = TimeFormat("HH:mm:ss.SSS")
        val FORMAT_TIME = TimeFormat("HH:mm:ss")

        val FORMATS = listOf(DEFAULT_FORMAT, FORMAT_TIME)

        fun parse(time: String): TimeSpan {
            var lastError: Throwable? = null
            for (format in FORMATS) {
                try {
                    return format.parse(time)
                } catch (e: Throwable) {
                    lastError = e
                }
            }
            throw lastError!!
        }

        operator fun invoke(pattern: String) = PatternTimeFormat(pattern)
    }
}

fun TimeFormat.parse(str: String, doAdjust: Boolean = true): TimeSpan =
    tryParse(str, doThrow = true, doAdjust = doAdjust) ?: throw DateException("Not a valid format: '$str' for '$this'")
fun TimeFormat.parseTime(str: String): LocalTime = parse(str).let { LocalTime(it.inWholeHours.toInt(),it.inWholeMinutes.toInt(),it.inWholeSeconds.toInt()) }

fun TimeFormat.format(time: Double): String = format(time.milliseconds)
fun TimeFormat.format(time: Long): String = format(time.milliseconds)

//fun TimeFormat.format(time: LocalTime): String = format(time.encoded)

internal fun Int.padded(count: Int): String {
    // @TODO: Handle edge case Int.MIN_VALUE that could not be represented as abs
    val res = this.absoluteValue.toString().padStart(count, '0')
    return if (this < 0) return "-$res" else res
}
internal fun Double.padded(intCount: Int, decCount: Int): String {
    val intPart = floor(this).toInt()
    val decPart = round((this - intPart) * 10.0.pow(decCount)).toInt()
    return "${intPart.padded(intCount).substringUseLength(-intCount, intCount)}.${decPart.toString().padStart(decCount, '0').substringUseLength(-decCount)}"
}

internal fun MicroStrReader.readTimeZoneOffset(tzNames: TimezoneNames = TimezoneNames.DEFAULT): TimeSpan? {
    val reader = this
    for ((name, offset) in tzNames.namesToOffsets) {
        if (name == "GMT" || name == "UTC") continue
        if (reader.tryRead(name)) return offset.toDuration()
    }
    if (reader.tryRead('Z')) return 0.minutes
    var sign = +1
    reader.tryRead("GMT")
    reader.tryRead("UTC")
    if (reader.tryRead("+")) sign = +1
    if (reader.tryRead("-")) sign = -1
    val part = reader.readRemaining().replace(":", "")
    val hours = part.substringUseLength(0, 2).padStart(2, '0').toIntOrNull() ?: return null
    val minutes = part.substringUseLength(2, 2).padStart(2, '0').toIntOrNull() ?: return null
    val roffset = hours.hours + minutes.minutes
    return if (sign > 0) +roffset else -roffset
}